﻿#region MIT
/*
Engine Of Evermore (http://code.google.com/p/engineofevermore/)
Copyright (c) 2008, 2009 Michael Woerister

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EngineOfEvermore.Scene;
using EngineOfEvermore.Scene.Visual;
using EngineOfEvermore.Common;
using System.Diagnostics;
using NUnit.Framework;
using System.ComponentModel;

namespace EngineOfEvermore.Editor.SceneEditor
{
    [SerializerIgnore]
    [Browsable( false )]
    class SceneSelectionComponent : SceneObjectComponent
    {
        class InternalDrawable : Drawable
        {
            SceneObject _sceneObject;

            public InternalDrawable( SceneObject sceneObject )
            {
                _sceneObject = sceneObject;
            }

            public override void Draw( System.Drawing.RectangleF visibleArea, 
                EngineOfEvermore.Imaging.IRenderer renderer )
            {
                VisualComponent vc = _sceneObject.GetComponent<VisualComponent>();

                System.Drawing.RectangleF rect = new System.Drawing.RectangleF();

                foreach ( VisualComponentChild vcc in vc.Children )
                {
                    if ( rect.IsEmpty )
                    {
                        rect = vcc.AABB;
                    }
                    else
                    {
                        if ( vcc.AABB.X < rect.X )
                            rect.X = vcc.AABB.X;

                        if ( vcc.AABB.Y < rect.Y )
                            rect.Y = vcc.AABB.Y;

                        if ( vcc.AABB.Right > rect.Right )
                            rect.Width = vcc.AABB.Right - rect.X;

                        if ( vcc.AABB.Bottom > rect.Bottom )
                            rect.Height = vcc.AABB.Bottom - rect.Y;
                    }
                }

                this._aabb = rect;

                renderer.Color = System.Drawing.Color.Green;
                renderer.DrawRect( rect );
            }
        }

        InternalDrawable _drawable;

        protected override void OnAddToSceneObject( SceneObject sceneObject )
        {
            base.OnAddToSceneObject( sceneObject );

            _drawable = new InternalDrawable( sceneObject );
        }

        protected override void OnRemoveFromSceneObject( SceneObject sceneObject )
        {
            _drawable = null;

            base.OnRemoveFromSceneObject( sceneObject );
        }

        protected override void OnAddToScene( SceneGraph scene )
        {
            base.OnAddToScene( scene );

            var visualSubSystem = scene.GetSubSystem<VisualSubSystem>();
            visualSubSystem.RegisterDrawable( _drawable );
        }

        protected override void OnRemoveFromScene( SceneGraph scene )
        {
            var visualSubSystem = scene.GetSubSystem<VisualSubSystem>();
            visualSubSystem.UnregisterDrawable( _drawable );

            base.OnRemoveFromScene( scene );
        }

        public override SceneObjectComponent Clone()
        {
            return new SceneSelectionComponent();
        }
    }

    public class SceneSelection : ISceneSelection
    {
        #region Variables
        readonly List<SceneObject> _selection = new List<SceneObject>();
        #endregion

        #region Events
        public event Action<ISceneSelection> SelectionChanged;
        #endregion

        #region Properties
        public int Count
        {
            get { return _selection.Count; }
        }
        #endregion

        #region Public Methods
        public void Add( SceneObject sceneObject )
        {
            Debug.Assert( sceneObject != null );

            if ( !_selection.Contains( sceneObject ) )
            {
                _selection.Add( sceneObject );

                Debug.Assert( sceneObject.GetComponent<SceneSelectionComponent>() == null );
                sceneObject.AddComponent( new SceneSelectionComponent() );

                _raiseSelectionChangedEvent();
            }

            Debug.Assert( _selectionDoesNotContainAnyDuplicates() );
            Debug.Assert( _allSelectedObjectsHaveSelectionComponent() );
        }

        public void Add( IEnumerable<SceneObject> items )
        {
            Debug.Assert( !items.Contains( null ) );

            var diff = items.Except( _selection ).ToArray();
            
            if ( diff.Any() )
            {
                _selection.AddRange( diff );

                foreach ( var item in diff )
                {
                    Debug.Assert( item.GetComponent<SceneSelectionComponent>() == null );
                    item.AddComponent( new SceneSelectionComponent() );
                }

                _raiseSelectionChangedEvent();
            }

            Debug.Assert( _selectionDoesNotContainAnyDuplicates() );
            Debug.Assert( _allSelectedObjectsHaveSelectionComponent() );
        }

        public void Remove( SceneObject item )
        {
            Debug.Assert( item != null );

            if ( _selection.Contains( item ) )
            {
                Debug.Assert( item.GetComponent<SceneSelectionComponent>() != null );
                item.RemoveComponent( item.GetComponent<SceneSelectionComponent>() );

                _selection.Remove( item );
                _raiseSelectionChangedEvent();
            }

            Debug.Assert( item.GetComponent<SceneSelectionComponent>() == null );
            Debug.Assert( _selectionDoesNotContainAnyDuplicates() );
            Debug.Assert( _allSelectedObjectsHaveSelectionComponent() );
        }

        public void Remove( IEnumerable<SceneObject> items )
        {
            Debug.Assert( !items.Contains( null ) );

            var intersection = items.Intersect( _selection ).ToArray();

            if ( intersection.Any() )
            {
                foreach ( SceneObject item in intersection )
                {
                    Debug.Assert( item.GetComponent<SceneSelectionComponent>() != null );
                    item.RemoveComponent( item.GetComponent<SceneSelectionComponent>() );

                    _selection.Remove( item );
                }

                _raiseSelectionChangedEvent();
            }

            Debug.Assert( !items.Any( item => item.GetComponent<SceneSelectionComponent>() != null ) );
            Debug.Assert( _selectionDoesNotContainAnyDuplicates() );
            Debug.Assert( _allSelectedObjectsHaveSelectionComponent() );
        }

        public void Set( IEnumerable<SceneObject> items )
        {
            Debug.Assert( !items.Contains( null ) );

            if ( items.IsEquivalentTo( _selection ) )
            {
                return;
            }

            var intersection = _selection.Intersect( items ).ToArray();
            var itemsToBeRemoved = _selection.Except( items ).ToArray();
            var itemsToBeAdded = items.Except( intersection  ).ToArray();

            Debug.Assert( itemsToBeRemoved.IsEquivalentTo( itemsToBeRemoved.Distinct() ) );
            Debug.Assert( itemsToBeAdded.IsEquivalentTo( itemsToBeAdded.Distinct() ) );
            Debug.Assert( !( itemsToBeRemoved.Intersect( itemsToBeAdded ).Any() ) );

            foreach ( SceneObject item in itemsToBeRemoved )
            {
                _selection.Remove( item );

                Debug.Assert( item.GetComponent<SceneSelectionComponent>() != null );
                item.RemoveComponent( item.GetComponent<SceneSelectionComponent>() );
            }

            foreach ( SceneObject item in itemsToBeAdded )
            {
                _selection.Add( item );

                Debug.Assert( item.GetComponent<SceneSelectionComponent>() == null );
                item.AddComponent( new SceneSelectionComponent() );
            }

            Debug.Assert( _selection.IsEquivalentTo( items ) );

            _raiseSelectionChangedEvent();

            Debug.Assert( _selectionDoesNotContainAnyDuplicates() );
            Debug.Assert( itemsToBeRemoved.All( so => so.GetComponent<SceneSelectionComponent>() == null ) );
            Debug.Assert( _allSelectedObjectsHaveSelectionComponent() );
        }

        public void Set( SceneObject sceneObject )
        {
            Debug.Assert( sceneObject != null );

            this.Set( new SceneObject[] { sceneObject } );

            Debug.Assert( _selectionDoesNotContainAnyDuplicates() );
            Debug.Assert( _allSelectedObjectsHaveSelectionComponent() );
        }

        public void Clear()
        {
            if ( _selection.Any() )
            {
                foreach ( SceneObject so in _selection )
                {
                    Debug.Assert( so.GetComponent<SceneSelectionComponent>() != null );
                    so.RemoveComponent( so.GetComponent<SceneSelectionComponent>() );
                }

                _selection.Clear();
                _raiseSelectionChangedEvent();
            }
        }

        public IEnumerator<SceneObject> GetEnumerator()
        {
            return _selection.GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }
        #endregion

        #region Private Methods
        private void _raiseSelectionChangedEvent()
        {
            if ( this.SelectionChanged != null )
            {
                this.SelectionChanged( this );
            }
        }

        private bool _allSelectedObjectsHaveSelectionComponent()
        {
            return _selection.All( so => so.GetComponent<SceneSelectionComponent>() != null );
        }

        private bool _selectionDoesNotContainAnyDuplicates()
        {
            return _selection.IsEquivalentTo( _selection.Distinct() );
        }
        #endregion

        #if ENGINE_OF_EVERMORE_ENABLE_UNIT_TESTS
        [TestFixture]
        public class Test
        {
            [Test]
            public void TestAddSingleItem()
            {
                var selection = new SceneSelection();
                var sceneObject = new SceneObject();

                selection.Add( sceneObject );

                Assert.That( selection.Count, Is.EqualTo( 1 ) );
                Assert.That( selection.Single(), Is.SameAs( sceneObject ) );
                Assert.That( sceneObject.GetComponent<SceneSelectionComponent>(), Is.Not.Null );
                Assert.That( _containsDuplicateSelectionComponents( sceneObject ), Is.False );
            }

            [Test]
            public void TestAddSingleItemToNonEmptySelection()
            {
                var selection = new SceneSelection();
                var items = new SceneObject[] { new SceneObject(), new SceneObject() };
                
                selection.Add( items[0] );
                selection.Add( items[1] );

                Assert.That( selection, Is.EquivalentTo( items ) );
                Assert.That( _allContainOneSelectionComponent( items ), Is.True );
            }

            [Test]
            public void TestAddRangeOfItems()
            {
                var selection = new SceneSelection();
                var items = new SceneObject[] { new SceneObject(), new SceneObject() };

                selection.Add( items );

                Assert.That( selection, Is.EquivalentTo( items ) );
                Assert.That( _allContainOneSelectionComponent( items ), Is.True );
            }

            [Test]
            public void TestAddRangeOfItemsToNonEmptySelection()
            {
                var selection = new SceneSelection();
                var items1 = new SceneObject[] { new SceneObject(), new SceneObject() };
                var items2 = new SceneObject[] { new SceneObject(), new SceneObject() };
                var allItems = items1.Union( items2 ).ToArray();

                selection.Add( items1 );
                selection.Add( items2 );

                Assert.That( selection, Is.EquivalentTo( allItems ) );
                Assert.That( _allContainOneSelectionComponent( allItems ), Is.True );
            }


            [Test]
            public void TestAddOverlapingRangesOfItems()
            {
                var selection = new SceneSelection();
                var items1 = new SceneObject[] { new SceneObject(), new SceneObject() };
                var items2 = new SceneObject[] { items1[0], new SceneObject() };
                var allItems = items1.Union( items2 ).ToArray();

                selection.Add( items1 );
                selection.Add( items2 );

                Assert.That( selection, Is.EquivalentTo( allItems ) );
                Assert.That( _allContainOneSelectionComponent( allItems ), Is.True );
            }

            [Test]
            public void TestRemoveSingleItem()
            {
                var selection = new SceneSelection();
                var sceneObject = new SceneObject();

                selection.Add( sceneObject );
                selection.Remove( sceneObject );

                Assert.That( selection.Count, Is.EqualTo( 0 ) );
            }

            [Test]
            public void TestRemoveSingleItem2()
            {
                var selection = new SceneSelection();
                var items = Util.Generate( () => new SceneObject(), 3 ).ToArray();
                var remaining = new SceneObject[] { items[1], items[2] } ;

                selection.Add( items );
                selection.Remove( items[0] );

                Assert.That( selection, Is.EquivalentTo( remaining ) );
                Assert.That( _allContainOneSelectionComponent( remaining ) );
                Assert.That( _containsNoSelectionComponent( items[0] ) );
            }

            [Test]
            public void TestRemoveRange()
            {
                var selection = new SceneSelection();
                var items = Util.Generate( () => new SceneObject(), 6 ).ToArray();
                var removedItems = items.Take( 3 ).ToArray();
                var remainingItems = items.Except( removedItems ).ToArray();

                selection.Add( items );
                selection.Remove( removedItems );

                Assert.That( selection, Is.EquivalentTo( remainingItems ) );
                Assert.That( _allContainOneSelectionComponent( remainingItems ) );
                Assert.That( _noneContainsSelectionComponent( removedItems ) );
            }

            [Test]
            public void TestRemoveRangeWithItemsThatAreNotContained()
            {
                var selection = new SceneSelection();
                var allItems = Util.Generate( () => new SceneObject(), 4 ).ToArray();
                var containedItems = allItems.Take( 2 ).ToArray();
                var removedItems = allItems.Skip( 1 ).ToArray();
                var remainingItems = allItems.Take( 1 ).ToArray();

                selection.Add( containedItems );
                selection.Remove( removedItems );

                Assert.That( selection, Is.EquivalentTo( remainingItems ) );
                Assert.That( _allContainOneSelectionComponent( remainingItems ) );
                Assert.That( _noneContainsSelectionComponent( allItems.Except( remainingItems ) ) );
            }

            [Test]
            public void TestSetOnEmptySelection()
            {
                var selection = new SceneSelection();
                var items = Util.Generate( () => new SceneObject(), 3 ).ToArray();

                selection.Set( items );

                Assert.That( selection, Is.EquivalentTo( items ) );
                Assert.That( _allContainOneSelectionComponent( items ) );
            }

            [Test]
            public void TestSetOnNonEmptySelection()
            {
                var selection = new SceneSelection();
                var prevItems = Util.Generate( () => new SceneObject(), 4 ).ToArray();
                var items = Util.Generate( () => new SceneObject(), 3 ).ToArray();

                selection.Add( prevItems );
                selection.Set( items );

                Assert.That( selection, Is.EquivalentTo( items ) );
                Assert.That( _allContainOneSelectionComponent( items ) );
                Assert.That( _noneContainsSelectionComponent( prevItems ) );
            }

            [Test]
            public void TestSetWithOverlappingRanges()
            {
                var selection = new SceneSelection();
                var prevItems = Util.Generate( () => new SceneObject(), 5 ).ToArray();
                var items = new SceneObject[] { prevItems[1], prevItems[2], new SceneObject() };

                selection.Add( prevItems );
                selection.Set( items );

                Assert.That( selection, Is.EquivalentTo( items ) );
                Assert.That( _allContainOneSelectionComponent( items ) );
                Assert.That( _noneContainsSelectionComponent( prevItems.Except( items ) ) );
            }

            [Test]
            public void TestSetEmpty()
            {
                var selection = new SceneSelection();
                var prevItems = Util.Generate( () => new SceneObject(), 5 ).ToArray();
                var emptyItems = Enumerable.Empty<SceneObject>();

                selection.Add( prevItems );
                selection.Set( emptyItems );

                Assert.That( selection, Is.Empty );
                Assert.That( _noneContainsSelectionComponent( prevItems ) );
            }

            private bool _containsDuplicateSelectionComponents( SceneObject sceneObject )
            {
                int count = 0;

                for ( int i = 0; i < sceneObject.ComponentCount; ++i )
                {
                    if ( sceneObject.GetComponent( i ) is SceneSelectionComponent )
                    {
                        ++count;
                    }
                }

                return count > 1;
            }

            private bool _allContainOneSelectionComponent( IEnumerable<SceneObject> items )
            {
                foreach ( var item in items )
                {
                    int selectionComponentCount = 0;

                    for ( int i = 0; i < item.ComponentCount; ++i )
                    {
                        if ( item.GetComponent( i ) is SceneSelectionComponent )
                        {
                            ++selectionComponentCount;
                        }
                    }

                    if ( selectionComponentCount != 1 )
                        return false;
                }

                return true;
            }

            private bool _containsNoSelectionComponent( SceneObject item )
            {
                return item.GetComponent<SceneSelectionComponent>() == null;
            }

            private bool _noneContainsSelectionComponent( IEnumerable<SceneObject> items )
            {
                return items.All( item => _containsNoSelectionComponent( item ) );
            }
        }
        #endif
    }
}
